package com.wm.blog.center.common.utils.es;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.wm.blog.center.common.annotation.es.EsField;
import com.wm.blog.center.common.annotation.es.EsId;
import com.wm.blog.center.common.annotation.es.EsIndex;
import com.wm.blog.center.common.enums.es.AnalyzerType;
import com.wm.blog.center.common.enums.es.FieldType;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.support.replication.ReplicationResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.RestStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Objects;

/**
 * @Author: weiming
 * @Date: 2021/5/26 16:58
 * @Description:
 */
@Component
@Slf4j
public class MyElasticUtils {

    @Autowired
    private RestHighLevelClient restHighLevelClient;


    /*---doc操作---*/
    public Boolean insertDoc(Object o) throws Exception {
        EsIndex declaredAnnotation = (EsIndex) o.getClass().getDeclaredAnnotation(EsIndex.class);
        IndexResponse indexResponse;
        if (Objects.isNull(declaredAnnotation)){
            indexResponse = insertDoc(o, StrUtil.toUnderlineCase(o.getClass().getSimpleName()));
        } else {
            indexResponse = insertDoc(o, declaredAnnotation.indexName());
        }

        indexResponse.getId();

        return indexResponse.getResult() == DocWriteResponse.Result.CREATED;
    }
    public IndexResponse insertDoc(Object o, String indexName) throws Exception {
        IndexRequest request = new IndexRequest(indexName);
        // 如果存在id则不覆盖，不设置的话，会进行覆盖
        request.opType(DocWriteRequest.OpType.CREATE);
        Field idField = getFieldByAnnotation(o, EsId.class);
        if(Objects.nonNull(idField)){
            idField.setAccessible(true);
            request.id(idField.get(o).toString());
        } else {
            idField = getFieldByName(o, "id");
            if (Objects.nonNull(idField)){
                idField.setAccessible(true);
                request.id(idField.get(o).toString());
            }
        }
        String reqJson = JSON.toJSONString(o);
        request.source(reqJson, XContentType.JSON);
        IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        return indexResponse;
    }


    public Boolean deleteDoc(String docId, String indexName) throws Exception {
        DeleteRequest request = new DeleteRequest(indexName, docId);
        request.opType();
        DeleteResponse deleteResponse = restHighLevelClient.delete(request, RequestOptions.DEFAULT);

        Boolean result = deleteResponse.getResult() == DocWriteResponse.Result.DELETED;
        ReplicationResponse.ShardInfo shardInfo = deleteResponse.getShardInfo();
        if (shardInfo.getFailed() > 0) {
            for (ReplicationResponse.ShardInfo.Failure failure :
                    shardInfo.getFailures()) {
                String reason = failure.reason();
                log.error("ES删除文档出现错误！docId={}, indexName={}, reason={}", docId, indexName, reason);
            }
        }
        return result;
    }

    public Boolean updateDoc(Object o) throws Exception {
        EsIndex declaredAnnotation = (EsIndex) o.getClass().getDeclaredAnnotation(EsIndex.class);
        String indexName;
        if (Objects.isNull(declaredAnnotation)){
            indexName = StrUtil.toUnderlineCase(o.getClass().getSimpleName());
        } else {
            indexName = declaredAnnotation.indexName();
        }
        Field idField = getFieldByAnnotation(o, EsId.class);
        String id = "";
        if(Objects.nonNull(idField)){
            idField.setAccessible(true);
            id = idField.get(o).toString();
        } else {
            idField = getFieldByName(o, "id");
            if (Objects.nonNull(idField)){
                idField.setAccessible(true);
                id = idField.get(o).toString();
            }
        }
        UpdateRequest updateRequest = new UpdateRequest(indexName, id);
        UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        Boolean result = updateResponse.getResult() == DocWriteResponse.Result.UPDATED;
        return result;
    }

    /*---index操作---*/
    public boolean existsIndex(Class clazz) throws Exception {
        EsIndex declaredAnnotation = (EsIndex) clazz.getDeclaredAnnotation(EsIndex.class);
        if (Objects.isNull(declaredAnnotation)){
            return existsIndex(StrUtil.toUnderlineCase(clazz.getSimpleName()));
        } else {
            return existsIndex(declaredAnnotation.indexName());
        }
    }

    public boolean existsIndex(String indexName) throws Exception {
        GetIndexRequest request = new GetIndexRequest(indexName);
        request.humanReadable(true);
        return restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
    }

    public boolean createIndexIfNotExist(Class clazz) throws Exception {
        if (!existsIndex(clazz)){
            return createIndex(clazz);
        } else {
            return true;
        }
    }

    public boolean createIndex(Class clazz) throws Exception{
        EsIndex declaredAnnotation = (EsIndex) clazz.getDeclaredAnnotation(EsIndex.class);
        if (Objects.isNull(declaredAnnotation)){
            return createIndex(StrUtil.toUnderlineCase(clazz.getSimpleName()), generateBuilder(clazz));
        } else {
            return createIndex(declaredAnnotation.indexName(), generateBuilder(clazz));
        }
    }

    public boolean createIndex(String indexName, XContentBuilder builder) throws Exception {
        Settings settings = Settings.builder()
                .put("index.number_of_shards", 5)
                .put("index.number_of_replicas", 1)
                .build();
        return createIndex(indexName, builder, settings);
    }


    public boolean createIndex(String indexName, XContentBuilder builder, Settings settings) throws Exception {
        CreateIndexRequest request = new CreateIndexRequest(indexName);
        request.settings(settings);
        request.mapping(builder);
        CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
        if (createIndexResponse.isAcknowledged() && createIndexResponse.isShardsAcknowledged()){
            return true;
        } else {
            return false;
        }
    }

    public boolean deleteIndex(Class clazz) throws Exception {
        EsIndex declaredAnnotation = (EsIndex) clazz.getDeclaredAnnotation(EsIndex.class);
        if (Objects.isNull(declaredAnnotation)){
            return deleteIndex(StrUtil.toUnderlineCase(clazz.getSimpleName()));
        } else {
            return deleteIndex(declaredAnnotation.indexName());
        }
    }


    public boolean deleteIndex(String indexName) throws Exception {
        try {
            DeleteIndexRequest request = new DeleteIndexRequest(indexName);
            AcknowledgedResponse deleteIndexResponse = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
            return deleteIndexResponse.isAcknowledged();
        } catch (ElasticsearchException e) {
            if (e.status() == RestStatus.NOT_FOUND){
                return true;
            } else {
                throw e;
            }
        }
    }

    public void updateIndex(String indexName, XContentBuilder builder) throws Exception {

        PutMappingRequest request = new PutMappingRequest(indexName);
        request.source(builder);
        AcknowledgedResponse putMappingResponse = restHighLevelClient.indices().putMapping(request, RequestOptions.DEFAULT);

    }

    public XContentBuilder generateBuilder(Class clazz) throws Exception{
        return generateBuilder(clazz, null, null);
    }

    private XContentBuilder generateBuilder(Class clazz, XContentBuilder builder, String objectName) throws Exception {
        if (Objects.isNull(builder)){
            builder = XContentFactory.jsonBuilder();
        }

        if (StrUtil.isNotBlank(objectName)){
            builder.startObject(objectName);
        }else {
            builder.startObject();
        }

        {
            builder.startObject("properties");
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(EsField.class)) {
                    // 如果有EsField注解
                    EsField fieldAnnotation = field.getDeclaredAnnotation(EsField.class);
                    if (!fieldAnnotation.exist()){
                        continue;
                    }
                    if (fieldAnnotation.type().getType().equals(FieldType.OBJECT.getType())){
                        // 如果是对象
                        builder = generateBuilder(field.getType(), builder, field.getName());
                    } else {

                        builder.startObject(field.getName());
                        {
                            builder.field("type", fieldAnnotation.type().getType());

                            if (fieldAnnotation.type().getType().equals(FieldType.TEXT.getType())){
                                builder.field("analyzer", fieldAnnotation.analyzer().getType());
                            }
                            if (fieldAnnotation.type().getType().equals(FieldType.DATE.getType())){
                                builder.field("format", fieldAnnotation.format());
                            }
                            if (!fieldAnnotation.index()){
                                builder.field("index", fieldAnnotation.index());
                            }
                        }
                        builder.endObject();
                    }
                } else {
                    // 没有EsField注解，则执行一些默认的操作
                    builder.startObject(field.getName());
                    if (field.getType().getSimpleName().equals("String")){
                        builder.field("type", FieldType.TEXT);
                        builder.field("analyzer", AnalyzerType.IK_SMART);
                    } else if (field.getType().getSimpleName().equals("Integer")
                            || field.getType().getSimpleName().equals("int")){
                        builder.field("type", FieldType.INTEGER);
                    } else if (field.getType().getSimpleName().equals("Long")
                            || field.getType().getSimpleName().equals("long")){
                        builder.field("type", FieldType.LONG);
                    } else if (field.getType().getSimpleName().equals("Double")
                            || field.getType().getSimpleName().equals("double")
                            || field.getType().getSimpleName().equals("Float")
                            || field.getType().getSimpleName().equals("float")
                            ){
                        builder.field("type", FieldType.DOUBLE);

                    } else if (field.getType().getSimpleName().equals("Date")){
                        builder.field("type", FieldType.DATE);
                        builder.field("format", "yyyy-MM-dd HH:mm:ss");
                    } else {
                        builder.field("type", FieldType.TEXT);
                    }

                    builder.endObject();
                }
            }
            builder.endObject();
        }
        builder.endObject();
        return builder;
    }
    private Field getFieldByAnnotation(Object o ,Class annotationClass){
        Field[] declaredFields = o.getClass().getDeclaredFields();
        if (declaredFields != null && declaredFields.length >0) {
            for(Field f : declaredFields){
                if (f.isAnnotationPresent(annotationClass)) {
                    return f;
                }
            }
        }
        return null;
    }
    private Field getFieldByName(Object o ,String name){
        Field[] declaredFields = o.getClass().getDeclaredFields();
        if (declaredFields != null && declaredFields.length >0) {
            for(Field f : declaredFields){
                if (f.getName().equals(name)) {
                    return f;
                }
            }
        }
        return null;
    }
}
